<script setup>
import Nav from "@/components/Nav.vue";
import {ref, watch} from "vue";

const accessKeyId = "LTAI5tQsNueNMR7DDMYjKjTS"
const accessKeySecret = "S6cTf8U6GSRQGkXNWfRMWOoEI0gfJf"
const bucket = "imooc-electron-vue3"
const region = "oss-cn-beijing"

import OSS from "ali-oss";

const client = new OSS({
  accessKeyId,
  accessKeySecret,
  region,
  bucket,
})

const fileList = ref([])
const getFileListFromOss = async () => {
  client.list().then((data) => {
    fileList.value = data.objects
    fileNameList = data.objects.map(item => item.name)
  }).catch((err) => {
    console.log(err)
  })
}
getFileListFromOss()

const active = ref(0);
const checkFileIndex = ref(-1);
const checkFileLeft = ref(0);
const checkFileTop = ref(0);
const contextmenuFile = (event, index) => {
  checkFileLeft.value = event.clientX;
  checkFileTop.value = event.clientY;
  checkFileIndex.value = index
}
const clearAll = () => {
  checkFileIndex.value = -1;
}
const convertSize = (size) => {
  size = size / 1024 / 1024;
  return size.toFixed(2) + "M"
}
const convertProgress = (size, current) => {
  return (current / size * 100).toFixed(2);
}


const dbClickFile = (index) => {
  console.log("左键双击下标为 " + index + " 的文件")
}
let timeoutCallback = null;
let deleteNumber = 0
const deleteFile = () => {
  console.log("删除下标为 " + checkFileIndex.value + " 的文件")
  const file = fileList.value[checkFileIndex.value]
  client.delete(file.name).then((res) => {
    deleteNumber++
    clearTimeout(timeoutCallback)
    timeoutCallback = setTimeout(() => {
      tools.ipcSend('notify', {
        title: '删除成功',
        body: deleteNumber === 1 ? file.name + "删除成功" : "多个文件删除成功",
        icon: '/logo.png'
      })
      deleteNumber = 0
    }, 2000)

    getFileListFromOss()
  }).catch(res => {
    tools.ipcSend('notify', {
      title: '删除失败',
      body: file.name,
      icon: '/logo.png'
    })
  })
}

const cancelDownload = (index) => {
  let fileData = downLoadFileList.value[index]
  downLoadFileList.value[index].canceled = true
  tools.ipcSend('notify', {
    title: '文件已取消下载',
    body: fileData.name,
    icon: '/logo.png'
  })
  downLoadFileList.value = downLoadFileList.value.filter((item) => {
    return item.uuid !== fileData.uuid
  })
  tools.ipcInvoke('deleteSystemFile', fileData.fileSavePath)
}

const continueDownload = (index) => {
  let fileData = downLoadFileList.value[index]
  downLoadFromOss(fileData.name, fileData.size, fileData.fileSavePath, fileData.uuid, {start: fileData.downloadSize + 1})
}
const downLoadFromOss = (name, size, fileSavePath, uuid, opt = {}) => {
  let work
  if (size <= fileCPoint) {
    work = client.get(name).then((data) => {
      return tools.ipcInvoke('writeSystemFile', fileSavePath, data.content, '')
    })
  } else {
    work = downloadBigFile(name, size, {
      ...opt,
      chunkCallback: function (current, data) {
        let canceled = true
        downLoadFileList.value.map(item => {
          if (item.uuid === uuid) {
            item.downloadSize = current
            canceled = item.canceled
          }
          return item
        })
        if (canceled) {
          throw 'canceled'
        }
        return tools.ipcInvoke('appendSystemFile', fileSavePath, data.content, '')
      }
    })
  }

  work.then(() => {
    tools.ipcSend('notify', {
      title: '文件下载成功',
      body: fileSavePath,
      icon: '/logo.png'
    })
    downLoadFileList.value = downLoadFileList.value.filter((item => item.uuid !== uuid))
  }).catch(err => {
    if (err.err !== 'canceled') {
      downLoadFileList.value.map((item) => {
        if (item.uuid === uuid) {
          item.status = 'error'
        }
        return item
      })
      tools.ipcSend('notify', {
        title: '文件下载失败',
        body: fileSavePath,
        icon: '/logo.png'
      })
    }
  })
}

const downloadBigFile = (name, size, opt = {}) => {
  return new Promise((resolve, reject) => {
    const chunk = opt.chunk ?? 1024 * 1024
    const start = opt.start ?? 0
    const end = opt.end ?? 0
    const chunkCallback = opt.chunkCallback ?? function () {
    }

    const run = (s) => {
      let end = Math.min(s + chunk, size)
      client.get(name, {
        headers: {
          Range: `bytes=${s}-${end}`
        }
      }).then((data) => {
        let status = chunkCallback(end, data)
        if (status instanceof Promise) {
          status.then(() => {
            if (end < size) {
              run(end + 1)
            } else {
              resolve()
            }
          }).catch(e => {
            reject({
              err: e,
              end
            })
          })
        } else if (status === false) {
          reject({
            err: '',
            end
          })
        } else {
          if (end < size) {
            run(end + 1)
          } else {
            resolve()
          }
        }
      }).catch((err) => {
        reject({
          err,
          end
        })
      })
    }
    run(start)
  })
}

const downloadFile = () => {
  console.log("下载下标为 " + checkFileIndex.value + " 的文件")
  const file = fileList.value[checkFileIndex.value]
  const fileName = file.name
  let suffixArr = fileName.split('.')
  const suffix = suffixArr[suffixArr.length - 1]
  tools.ipcInvoke('saveFile', {
    title: '选择保存文件的位置',
    buttonLabel: '下载',
    filters: [
      {
        name: suffix,
        extensions: [suffix]
      }
    ],
    defaultPath: fileName
  }).then(res => {
    if (res.canceled) return
    const fileSavePath = res.filePath
    const uuid = Date.now()
    downLoadFileList.value.push({
      uuid,
      size: file.size,
      downloadSize: 0,
      name: file.name,
      status: 'success',
      fileSavePath,
      canceled: false
    })
    downLoadFromOss(file.name, file.size, fileSavePath, uuid)
  })
}

const _ = localStorage.getItem(('uploadFileList'))

const uploadFileList = ref(_ === null || _ === '' || _ === undefined ? [] : JSON.parse(_))

const downLoadFileList = ref([])

const uploadFileEl = ref();

const getBlobFile = async (filePath) => {
  try {
    const {data, err} = await tools.ipcInvoke('readSystemFile', filePath, "")
    if (err !== null) {
      return false;
    }

    const baseName = await tools.ipcInvoke('getFileBasename', filePath);
    const fileType = await tools.ipcInvoke('getFileType', baseName)

    console.log(baseName, fileType, data)
    // uint8Array
    /// uint8Array => blob
    return new Blob([data], {
      type: fileType,
    });
  } catch {
    return false
  }
}

const checkUploadFile = async (path = "") => {
  let filePath = path
  if (filePath === "") {
    const getFileRes = await tools.ipcInvoke('getFile', {
      title: '请选择文件',
    })
    if (getFileRes.canceled) {
      return false
    }
    filePath = getFileRes.filePaths[0]
  }
  const baseName = await tools.ipcInvoke('getFileBasename', filePath)
  const file = await getBlobFile(filePath)

  const fileName = generateName(baseName)
  const uuid = Date.now()
  let uploadItem = {
    size: file.size,
    uploadSize: 0,
    name: fileName,
    uuid,
    uploadId: '',
    filePath,
    status: 'success',
    chunkDone: []
  }
  uploadFileList.value.push(uploadItem)
  fileNameList.push(fileName)

  uploadToOss(fileName, file, uuid)
}
let fileNameList = []
const generateName = (name, number = 1, oldName = "") => {
  if (!fileNameList.includes(name)) {
    return name
  }
  if (oldName !== "") {
    name = oldName
  }
  let nameArr = name.split(".")
  let prefix = nameArr.slice(0, nameArr.length - 1)
  prefix = prefix.join(".")

  let suffix = nameArr[nameArr.length - 1]
  let realName = `${prefix}(${number}).${suffix}`
  number++
  return generateName(realName, number, name)
}


const fileCPoint = 4 * 1024 * 1024;

const uploadToOss = (fileName, file, uuid, opt = {}) => {
  let work
  if (file.size <= fileCPoint) {
    work = client.put(fileName, file, {
      headers: {
        "x-oss-forbid-overwrite": "true"
      }
    })
  } else {
    //   大文件
    work = uploadBigFile(fileName, file, {
      ...opt,
      chunkCallback: function (current, index, uploadId) {
        uploadFileList.value.map(item => {
          if (item.uuid === uuid) {
            item.uploadSize = current
            item.uploadId = uploadId
          }
          return item
        })
      }
    })
  }
  work.then(res => {
    console.log(res)
    tools.ipcSend('notify', {
      title: "文件上传成功",
      body: fileName,
      icon: "/logo.png"
    })
    getFileListFromOss()
    uploadFileList.value = uploadFileList.value.filter((item => {
      return item.uuid !== uuid;
    }))
  }).catch(({err, chunkDone}) => {
    const status = err.code
    if (status === "FileAlreadyExists") {
      console.log('存在同名文件，上次失败')
    }
    uploadFileList.value = uploadFileList.value.map((item) => {
      console.log('item.uuid', item.uuid, uuid)
      if (item.uuid === uuid) {
        item.status = 'error'
        item.chunkDone = chunkDone
      }
      return item
    })
    console.log(status)
  })
}

const uploadBigFile = (name, file, opt = {}) => {
  return new Promise((resolve, reject) => {
    const chunk = opt.chunk ?? 1024 * 1024
    const chunkDone = opt.chunkDone ?? []
    const fileSize = file.size
    const start = opt.start ?? 0
    let uploadId = opt.uploadId ?? ""
    const chunkCallback = opt.chunkCallback ?? function () {
    }
    const run = (s, index = 1) => {
      const end = Math.min(s + chunk, fileSize)
      client.uploadPart(name, uploadId, index, file, s, end).then(chunkRes => {
        chunkDone.push({
          number: index,
          etag: chunkRes.etag
        })
        if (chunkCallback instanceof Function) {
          chunkCallback(end, index, uploadId)
        }
        index++
        if (end < fileSize) {
          run(end, index)
        } else {
          // 3、合并文件分片
          client.completeMultipartUpload(name, uploadId, chunkDone).then(completeRes => {
            resolve(completeRes)
          }).catch(err => {
            reject({err, chunkDone})
          })
        }
      }).catch(err => {
        reject({err, chunkDone})
      })
    }
    if (uploadId === "") {
      client.initMultipartUpload(name, {
        headers: {
          'x-oss-forbid-overwrite': 'true',
        }
      }).then(res => {
        uploadId = res.uploadId
        run(start)
      }).catch(err => {
        reject({err, chunkDone})
      })
    } else {
      run(start, chunkDone.length + 1)
    }
  })
}

watch(uploadFileList, (value) => {
  localStorage.setItem('uploadFileList', JSON.stringify(value))
})

const continueClick = async (index) => {
  let fileData = uploadFileList.value[index]
  const filePath = fileData.filePath
  const file = await getBlobFile(filePath)
  if (file === false) {
    return
  }
  uploadToOss(fileData.name, file, fileData.uuid, {
    chunkDone: fileData.chunkDone,
    uploadId: fileData.uploadId,
    start: fileData.uploadSize
  })
}

const cancelUpload = (index, count = 1) => {
  const file = uploadFileList.value[index]
  if (file.size > fileCPoint) {
    if (file.uploadId === '' && count < 5) {
      setTimeout(() => {
        count++
        cancelUpload(index, count)
      }, 200)
    } else {
      client.abortMultipartUpload(file.name, file.uploadId)
    }
  }
  uploadFileList.value = uploadFileList.value.filter((item) => {
    return item.uuid !== file.uuid;
  })
}

import Dragover from "@/components/Dragover.vue";

const dragoverIs = ref(false)
const dragover = (event) => {
  event.preventDefault()
  dragoverIs.value = true
}
const drop = (event) => {
  event.preventDefault()
  dragoverIs.value = false
  let files = event.dataTransfer.files
  let items = event.dataTransfer.items

  for (let i = 0; i < files.length; i++) {
    let item = items[i]
    const type = item.webkitGetAsEntry()
    if (type.isFile) {
      let file = files[i]
      checkUploadFile(file.path)
    }
  }
}
const dragleave = (event) => {
  event.preventDefault()
  dragoverIs.value = false
}
</script>

<template>
  <div class="container" @click="clearAll" @dragleave="dragleave" @dragover="dragover" @drop="drop">
    <Nav :index="2"/>
    <div class="container-box">
      <div class="card">
        <div class="card-nav">
          <div :class="{active:active===0}" class="item" @click="active=0">云盘</div>
          <div :class="{active:active===1}" class="item" @click="active=1">上传({{ uploadFileList.length }})
          </div>
          <div :class="{active:active===2}" class="item" @click="active=2">
            下载({{ downLoadFileList.length }})
          </div>
        </div>
        <div class="card-list">
          <div v-if="active === 0" class="card-list-item cloud-list">
            <div class="tab-bar">
              <div class="path-info">
                <div class="label">当前路径：</div>
                <div class="path">/</div>
              </div>
              <div class="operation">
                <input ref="uploadFileEl" style="display: none" type="file" @change="fileChange">
                <div class="button" @click="checkUploadFile('')">上传</div>
              </div>
            </div>
            <div class="list-box">
              <div v-for="(item,index) in fileList" :class="{'file-active':checkFileIndex === index}" :title="item.name"
                   class="file-item" @contextmenu="contextmenuFile($event,index)"
                   @dblclick="dbClickFile(index)">
                <div class="icon">
                  <svg class="icon" height="200" p-id="1599" t="1720983987241"
                       version="1.1" viewBox="0 0 1024 1024" width="200" xmlns="http://www.w3.org/2000/svg">
                    <path
                        d="M160 0h512l256 256v704c0 35.3472-28.6528 64-64 64H160c-35.3472 0-64-28.6528-64-64V64c0-35.3472 28.6528-64 64-64z"
                        fill="#CCCCCC" p-id="1600"></path>
                    <path d="M672 0l256 256h-192c-35.3472 0-64-28.6528-64-64V0z" fill="#EAEAEA"
                          p-id="1601"></path>
                    <path
                        d="M384 499.2c0-25.6 5.12-46.08 10.24-58.88 5.12-12.8 15.36-25.6 28.16-35.84 12.8-12.8 25.6-20.48 43.52-25.6 15.36-5.12 30.72-7.68 48.64-7.68 35.84 0 64 10.24 89.6 30.72C627.2 422.4 640 448 640 481.28c0 15.36-5.12 28.16-10.24 40.96s-17.92 28.16-38.4 46.08-28.16 30.72-35.84 38.4c-7.68 7.68-10.24 17.92-15.36 28.16-5.12 10.24-2.56 17.92-2.56 43.52h-51.2c0-25.6 2.56-38.4 5.12-51.2s7.68-23.04 15.36-33.28 15.36-23.04 33.28-40.96c17.92-17.92 30.72-30.72 35.84-38.4 5.12-7.68 10.24-20.48 10.24-38.4s-7.68-30.72-20.48-43.52-30.72-20.48-53.76-20.48c-51.2 0-76.8 35.84-76.8 87.04h-51.2z m153.6 281.6h-51.2v-51.2h51.2v51.2z"
                        fill="#FFFFFF" p-id="1602"></path>
                  </svg>
                </div>
                <div class="title">{{ item.name }}</div>
              </div>
              <div v-if="checkFileIndex !== -1" :style="`left: ${checkFileLeft}px;top: ${checkFileTop}px;`"
                   class="right-menu">
                <div class="right-menu-item" @click="downloadFile">
                  <div class="menu-icon"></div>
                  <div class="menu-text">下载</div>
                </div>
                <div class="right-menu-item" @click="deleteFile">
                  <div class="menu-icon"></div>
                  <div class="menu-text">删除</div>
                </div>
              </div>
            </div>
          </div>
          <div v-if="active === 1" class="card-list-item upload-list">
            <div v-for="(item, index) in uploadFileList" class="upload-item">
              <div class="left">
                <div class="icon"></div>
                <div class="file-info">
                  <div class="name">{{ item.name }}</div>
                  <div class="size">
                    <div class="current-size">{{ convertSize(item.uploadSize) }}</div>
                    /
                    <div class="all-size">{{ convertSize(item.size) }}</div>
                  </div>
                </div>
              </div>
              <div class="center">
                <div class="progress-info">
                  <div class="progress">
                    <div :style="`width:${convertProgress(item.size,item.uploadSize)}%;`" class="current"></div>
                  </div>
                  <div class="progress-text">{{ convertProgress(item.size, item.uploadSize) }}%</div>
                </div>
              </div>
              <div class="right">
                <div class="operation">
                  <div v-if="item.status === 'error'" class="button" @click="continueClick(index)">重试</div>
                  <div class="button" @click="cancelUpload(index)">取消</div>
                </div>
              </div>
            </div>
          </div>
          <div v-if="active === 2" class="card-list-item download-list">
            <div v-for="(item, index) in downLoadFileList" class="download-item">
              <div class="left">
                <div class="icon"></div>
                <div class="file-info">
                  <div class="name">{{ item.name }}</div>
                  <div class="size">
                    <div class="current-size">{{ item.downloadSize }}</div>
                    /
                    <div class="all-size">{{ item.size }}</div>
                  </div>
                </div>
              </div>
              <div class="center">
                <div class="progress-info">
                  <div class="progress">
                    <div :style="`width:${convertProgress(item.size,item.downloadSize)}%;`" class="current"></div>
                  </div>
                  <div class="progress-text">{{ convertProgress(item.size, item.downloadSize) }}%</div>
                </div>
              </div>
              <div class="right">
                <div class="operation">
                  <div v-if="item.status === 'error'" class="button" @click="continueDownload(index)">重试</div>
                  <div class="button" @click="cancelDownload(index)">取消</div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <Dragover v-if="dragoverIs"/>
  </div>
</template>

<style lang="scss" scoped>
.container {
  .container-box {
    position: fixed;
    bottom: 0;
    width: 100%;
    height: calc(100vh - 68px);
    margin-top: 68px;
    background: #f3f3f3;
    display: flex;
    justify-content: center;
    align-items: center;

    .card {
      width: 1400px;
      height: 800px;
      background: #fff;
      border-radius: 10px;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
    }
  }
}

.card-nav {
  width: 100%;
  display: flex;
  justify-content: space-around;
  border-bottom: 1px solid var(--base-border);

  .item {
    width: 100%;
    text-align: center;
    font-size: 18px;
    padding: 10px 0;
    box-sizing: border-box;
    cursor: pointer;
    transition: all .3s;
    border-top-right-radius: 10px;
    border-top-left-radius: 10px;
  }

  .active, .item:hover {
    color: #fff;
    background-color: var(--primary);
  }
}

.card-list {
  padding: 10px 0;
  height: calc(100% - 48px);
  width: 100%;

  .card-list-item {
    width: 100%;
    height: 100%;
    overflow-x: auto;

    &::-webkit-scrollbar {
      width: 6px;
    }

    &::-webkit-scrollbar-thumb {
      background-color: #0003;
      border-radius: 10px;
      transition: all .2s ease-in-out;
    }

    &::-webkit-scrollbar-track {
      border-radius: 10px;
    }
  }

  .cloud-list {
    user-select: none;
    -webkit-user-select: none;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;

    .tab-bar {
      padding: 0 10px;
      display: flex;
      justify-content: space-between;
      margin-bottom: 10px;
      align-items: center;

      .path-info {
        height: 100%;
        width: 100%;
        display: flex;
        align-items: center;

        .label {
          flex-shrink: 0;
        }

        .path {
          width: 100%;
          background-color: #f1f1f1;
          padding: 0 10px;
        }
      }

      .operation {
        flex-shrink: 0;
        display: flex;
        align-items: center;

        .button {
          margin: 0 20px;
          padding: 5px 16px;
          background-color: var(--primary);
          color: #fff;
          border-radius: 5px;
          transition: all .3s;
          cursor: pointer;

          &:hover {
            background-color: var(--primary06);
          }
        }
      }
    }

    .list-box {
      &::-webkit-scrollbar {
        width: 6px;
      }

      &::-webkit-scrollbar-thumb {
        background-color: #0003;
        border-radius: 10px;
        transition: all .2s ease-in-out;
      }

      &::-webkit-scrollbar-track {
        border-radius: 10px;
      }

      height: 100%;
      overflow-x: auto;
      display: grid;
      grid-template-columns: repeat(10, 2fr);
      align-content: flex-start;
      gap: 15px 0;

      .file-item {
        border-radius: 5px;
        margin: 0 auto;
        width: 120px;
        height: 140px;
        cursor: pointer;
        padding: 5px;
        box-sizing: border-box;
        border: 1px solid transparent;

        .icon {
          margin: 0 auto;
          width: 100px;
          height: 100px;
        }

        .title {
          text-align: center;
          margin-top: 8px;
          font-size: 14px;
          color: var(--primary-text);
          display: -webkit-box;
          overflow: hidden;
          -webkit-box-orient: vertical;
          -webkit-line-clamp: 1;
        }
      }

      .file-active, .file-item:hover {
        background-color: #fff;
        border: 1px solid var(--base-border);
      }

      .right-menu {
        box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2);
        border-radius: 5px;
        z-index: 999;
        border: 1px solid var(--base-border);
        width: 160px;
        position: fixed;
        left: 0;
        top: 0;
        padding: 10px;
        background-color: #fff;

        .right-menu-item {
          cursor: pointer;
          box-sizing: border-box;
          display: flex;
          align-items: center;
          padding: 5px 10px;
          border-radius: 5px;

          .menu-icon {
            width: 20px;
            height: 20px;
            border-radius: 999px;
            background-color: var(--primary);
          }

          .menu-text {
            margin-left: 5px;
            font-size: 18px;
          }
        }

        .right-menu-item:hover {
          background-color: var(--primary);

          .menu-icon {
            background-color: #fff;
          }

          .menu-text {
            color: #fff;
          }
        }
      }
    }


  }

  .upload-list, .download-list {
    padding: 0 10px;
    user-select: none;
    -webkit-user-select: none;

    .upload-item, .download-item {
      display: flex;
      align-items: center;
      align-content: center;
      justify-content: space-between;
      padding: 10px 20px;
      border-bottom: 1px solid var(--lighter-border);

      .left {
        width: 400px;
        display: flex;

        .icon {
          flex-shrink: 0;
          border-radius: 5px;
          margin: 0 10px 0 0;
          width: 30px;
          height: 30px;
          background-color: var(--primary);
        }

        .file-info {
          .name {
            display: -webkit-box;
            overflow: hidden;
            -webkit-box-orient: vertical;
            -webkit-line-clamp: 1;
            font-size: 16px;
            color: var(--primary-text);
          }

          .size {
            display: flex;
            font-size: 12px;
            color: var(--placeholder-text);
          }
        }
      }

      .center {
        .progress-info {
          .progress {
            width: 200px;
            height: 5px;
            background-color: #d4d4d4;
            border-radius: 9999px;

            .current {
              width: 10%;
              height: 100%;
              background-color: var(--primary);
              border-radius: 9999px;
            }
          }

          .progress-text {
            color: var(--placeholder-text);
            margin-top: 5px;
            line-height: 1;
            font-size: 14px;
          }
        }
      }

      .right {
        display: flex;

        .operation {
          display: flex;

          .button {
            margin: 0 5px;
            cursor: pointer;
            font-size: 16px;
            color: var(--regular-text);

            &:hover {
              color: var(--primary);
            }
          }

        }
      }
    }
  }
}

</style>
